#!/usr/bin/env python

# Imports
from xen.xend.xenstore.xsutil import *
from xen.xend.xenstore.xswatch import *

import os
import time
import re
import random

# Templates
throttleCmdTemplate = 'xm sched-credit -d $DOMID -c $CAP'
throttleCmdCapTemplate = ''
createCmdTemplate = 'xm create $CONF'

# Make sure we got a config as an argument
configFile = '/images/sysbench_cpu/conf_sysbench_cpu.cfg'

# Other global variables
vmName = ""
VCPUs = 1


# Sine behavior generator
def RunSine(stepSize, timeLen):

	# Determine the max cpu level based on the number of VCPUs
	maxCPU = VCPUs * 100;
	
	# Choose the number of iterations we want to take (up to 6, where 1 iteration = 1 ascent / descent)
	iterations = random.randint(1, 6)
	
	# debug
	print 'Generating sine curve with ' + str(iterations) + ' ascents/descents.'
	
	# Choose a random start point
	currLimit = 0
	if random.randint(0, 1) == 0:
		currLimit = 0
	else:
		currLimit = maxCPU

	# Run iterations
	for i in range(iterations):
	
		# Determine if we're ascending or descending
		currDirection = ''
		if currLimit == 0:
			currDirection = 'ascending'
		else:
			currDirection = 'descending'
	
		# Set continue condition true initially
		continueCond = True
		
		# Loop until continueCond is false for this iteration
		while continueCond:
		
			# Calculate sleep length
			sleepLen = 0
			if timeLen == 0:
				sleepLen = random.randint(2, 20)
			else:
				sleepLen = timeLen
				
			# Limit
			throttleCmd = throttleCmdCapTemplate.replace('$CAP', str(currLimit))
			os.system(throttleCmd)
			
			# Debug
			print '-> Limiting at ' + str(currLimit) + '% for ' + str(sleepLen) + 's.'
			
			# Sleep
			time.sleep(sleepLen)
			
			# Calculate delta limit
			deltaLimit = 0
			if stepSize == 0:
				deltaLimit = random.randint(1, 20)
			else:
				deltaLimit = stepSize
			
			# Increment/decrement currLimit
			if(currDirection == 'ascending'):
				currLimit += deltaLimit
			else:
				currLimit -= deltaLimit
			
			# Recalculate continue condition
			if(currDirection == 'ascending'):
				continueCond = (currLimit < maxCPU)
			else:
				continueCond = (currLimit > 0)
	
# Random behavior generator
def RunRand():

	# debug
	print 'Running completely random pattern.'

	# Determine the max cpu level based on the number of VCPUs
	maxCPU = VCPUs * 100;

	# Choose the number of steps we want to take (up to 20)
	steps = random.randint(1, 20)
	
	# debug
	print 'Running for ' + str(steps) + ' steps.'
	
	# Loop over steps
	for i in range(steps):
	
		# Choose a length of time (up to 20s)
		timeLen = random.randint(1, 20)
		
		# Choose a cpu level
		targetLevel = random.randint(1, maxCPU)
		
		# Execute limit
		throttleCmd = throttleCmdCapTemplate.replace('$CAP', str(targetLevel))
		os.system(throttleCmd)
		
		# Debug
		print '-> Running at ' + str(targetLevel) + '% for ' + str(timeLen) + 's.'
		
		# Sleep
		time.sleep(timeLen)

# Smooth sine generating function	
def RunSmoothSine():

	# debug
	print 'Running smooth sine pattern.'
	
	# Run the sine function with the correct parameters (stepSize = 5, timeLen = 5)
	RunSine(5, 5)

	
# Steep sine generating function
def RunSteepSine():

	# debug
	print 'Running steep sine pattern.'

	# Run the sine function with the correct parameter (stepSize = 10, timeLen = 5)
	RunSine(10, 5)


# Random sine generating function
def RunRandSine():

	# debug
	print 'Running random sine pattern.'

	# Run the sine function with the correct parameter (stepSize = 0 = random, timeLen = 0 = random)
	RunSine(0, 0)

# XenStore reader
def EnumerateDomainIDs():

	# Create a new dictionary
	ldomains = {}

	# Parse xm list to get the list of domain-id's that are currently running
	ldomIDs = []
	f=os.popen("xm list")
	for line in f:
		b=re.sub("\s+",'|',line)	
		x=b.split('|')
		if x[1]!="ID" and x[1]!="0": 
			ldomIDs.append(int(x[1]))

	# Use the XenStore handle to access the names of these domains
	xs = xshandle()
	for domID in ldomIDs:
		path = xs.get_domain_path(domID)	
		path +='/name'
		transaction = xs.transaction_start()
		ldomains[ xs.read(transaction,path) ] = domID
		xs.transaction_end(transaction)
		
	# Return
	return ldomains
		
		
# Xen config parser
def XenConfigParser(fileName):

	# Set vars to defaults
	vmname = ""
	vcpus = 1

	# Open the file
	conf = open(fileName, 'r')
	
	# Loop over all lines in the file
	for line in conf:
		
		# If this line contains the vm name (and not the hostname)
		if (line.lower().find('name') != -1) and (line.lower().find('hostname') == -1):
		
			# Split on spaces
			lineSplit = line.split(' ')
			
			# Save the last string, stripped of quotes
			vmname = lineSplit[2].strip().strip('"')
		
		# If this line contains the number of vcpus
		if line.lower().find('vcpus') != -1:

			# Split on spaces
			lineSplit = line.split(' ')
			
			# Save the last string, converted to an int
			vcpus = int(lineSplit[2].strip())

	# Return
	return vmname, vcpus

# Main function
def main():
	
	# Set global vars
	global vmName
	global VCPUs
	global throttleCmdCapTemplate
	
	# Seed the random generator
	random.seed()
	
	# Parse the config for the VM name and number of VCPUs
	vmName, VCPUs = XenConfigParser(configFile)

	# debug
	print 'Config contains VM name="' + vmName + '" with VCPUs=' + str(VCPUs)
	
	# Make sure we got a VM name at least
	if vmName == '':
		print 'ERROR: Did not get a VM name from the config file.'
		return

	# Parse xm list for the domain ids
	ldomains = EnumerateDomainIDs()

	# If the VM doesn't exist, create the vm
	if vmName not in ldomains:

		# debug
		print 'Domain ' + vmName + ' does not exist, creating.'

		# Create the VM
		createCmd = createCmdTemplate.replace('$CONF', configFile)
		os.system(createCmd)
		
		# Reparse xm list
		ldomains = EnumerateDomainIDs()

	# Set our domain id
	domainID = ldomains[vmName]		
		
	# Create a throttle command without the cap for convenience
	throttleCmdCapTemplate = throttleCmdTemplate.replace('$DOMID', str(domainID))
		
	# Limit the VM initially so we don't starve
	throttleCmd = throttleCmdCapTemplate.replace('$CAP', str(50))
	os.system(throttleCmd)

	# Loop forever
	while(1):
		
		# Choose a routine randomly
		routine = random.randint(0, 3)
		
		# Execute that routine
		if(routine == 0):
			RunSmoothSine()
		elif(routine == 1):
			RunSteepSine()
		elif(routine == 2):
			RunRandSine()
		elif(routine == 3):
			RunRand()

if __name__ == "__main__":
    main()
